using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Core;
using Core.Hardware.Watchdog;
using ModBus.IO;
using ModBus.IO.Protocols;
using SolarDevices.Configuration;

namespace SolarDevices
{
    public class ModbusTCPDevice : IModbusDevice, IDisposable
    {
        private const int POLL_PERIOD = 6000;
        private const int CountDownToDisconnect = 3;
        private int _lastUpdate = CountDownToDisconnect;

        private Timer _timer;
        private static ModbusTCPDevice _singleton;
        private ushort[] _data;
        private Socket _socket;
        private ModbusClient _driver;
        private ModbusCommand _command;
        private ICommClient _portClient;
        private readonly IModbusConfiguration _configuration;
        private bool _connected;
        private bool _timeSet;
        private readonly Watchdog _watchdog;

        public ModbusTCPDevice()
        {
            _configuration = new ModbusConfiguration();
            _watchdog = Watchdog.GetSingleton();
            _watchdog.RegisterTarget(Device.Modbus);
        }

        public void Dispose()
        {
            StopPolling();
            _singleton = null;
        }

        public static ModbusTCPDevice GetSingleton()
        {
            return _singleton ?? (_singleton = new ModbusTCPDevice());
        }

        public IModbusConfiguration Configuration
        {
            get { return _configuration; }
        }

        public void StartPolling()
        {
            if (_timer == null)
                SetupTimer();
        }

        private void SetupTimer()
        {
            _timer = new Timer(PollDevice, null, 1000, POLL_PERIOD);
        }

        public void StopPolling()
        {
            try
            {
                if (_timer != null)
                {
                    _timer.Dispose();
                }
                if (_socket != null)
                {
                    _socket.Close();
                }
            }
            catch (Exception)
            {

            }
            _timer = null;
            _socket = null;
            _portClient = null;
            _driver = null;
            _command = null;
        }

        private void PollDevice(object state)
        {
            try
            {

                if (_socket == null)
                {
                    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
                    _socket.SendTimeout = 2000;
                    _socket.ReceiveTimeout = 2000;
                    _lastUpdate = CountDownToDisconnect;
                }
                if (_portClient == null)
                {
                    try
                    {
                        var ipaddr = IPAddress.Parse(Configuration.ModbusSlaveIp);
                        var ept = new IPEndPoint(ipaddr, Configuration.ModbusSlavePort);
                        _socket.Connect(ept);
                        _portClient = _socket.GetClient();
                        _driver = new ModbusClient(new ModbusTcpCodec()) { Address = 1 };
                        _command = new ModbusCommand(ModbusCommand.FuncReadMultipleRegisters) { Offset = Address, Count = Count };
                        _watchdog.CallIn(Device.Modbus);
                    }
                    catch (Exception ex)
                    {
                        lock (this)
                        {
                            _connected = false;
                        }
                        _socket = null;
                        _portClient = null;
                        DebugLogger.TryLog(ex);
                        return;
                    }
                }
                if (_driver != null)
                {
                    try
                    {
                        //execute the command synchronously
                        var result = _driver.ExecuteGeneric(_portClient, _command);
                        if (result.Status == CommResponse.Ack)
                        {
                            lock (this)
                            {
                                _connected = true;
                                _data = _command.Data;
                                _lastUpdate = CountDownToDisconnect;
                            }
                        }
                        else
                        {
                            lock (this)
                            {
                                _connected = false;
                            }
                            _socket = null;
                            _portClient = null;
                            _driver = null;
                            _command = null;
                            DebugLogger.TryLog("Disconnected from modbus device");
                        }
                        if (--_lastUpdate != 0)
                            _watchdog.CallIn(Device.Modbus);
                    }
                    catch (Exception ex)
                    {
                        lock (this)
                        {
                            _connected = false;
                        }
                        _socket = null;
                        _portClient = null;
                        _driver = null;
                        _command = null;
                        DebugLogger.TryLog(ex);
                    }
                }

            }
            catch (Exception ex) // catch all
            {
                DebugLogger.TryLog(ex);
            }
        }

        public int Address { get; set; }
        public int Count { get; set; }

        public ushort[] GetData()
        {
            ushort[] rval;
            lock (this)
            {
                if (_data != null)
                {
                    rval = new ushort[_data.Length];
                    _data.CopyTo(rval, 0);
                }
                else
                {
                    rval = null;
                }
            }
            return rval;
        }

        public bool Connected
        {
            get
            {
                bool rval;
                lock (this)
                {
                    rval = _connected;
                }
                return rval;
            }
        }

    }
}